package compiler;

import java.io.FileReader;
import java.io.IOException;

import org.antlr.runtime.ANTLRFileStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.*;
import org.antlr.stringtemplate.StringTemplateGroup;

import compiler.common.*;
import compiler.emiter.EmiterEnv;
import compiler.emiter.JKlasyEmiter;
import compiler.emiter.JKlasyEmiter.program_return;
import compiler.lexer.JKlasyLexer;
import compiler.parser.JKlasyParser;
import compiler.parser.ParsingErrorException;
import compiler.typesinit.JKlasyTypesInit;
import compiler.typesinit.TypesInitErrorException;
import compiler.typechecker.JKlasyTypeChecker;
import compiler.typechecker.TypeCheckerErrorException;

import java.util.List;
import java.util.Map;
import java.util.HashMap;

/**
 * Main JKlasy compiler class.
 * 
 * @author Karol Stosiek (karol.stosiek@gmail.com)
 * @author Michal Anglart (anglart.michal@gmail.com)
 */
public class JKlasyCompiler {
  
  public static int EXIT_SUCCESS = 0;
  public static int EXIT_FAILURE = 1;
  
  
  private String getFileName(String address) {
    String[] tab = address.split("/");
    address = tab[tab.length - 1];
    address = address.substring(0, address.length() - 4);
    address = address.substring(0,1).toUpperCase() + 
              address.substring(1, address.length());
    return address;
  }
  
  /**
   * Main routine. 
   * JKlasy compiler usage: java -jar jklasy filename.jkl
   * The *.jkl extension is purely virtual - the compiler 
   * ignores it.
   *
   * @param args - command line arguments 
   */
  public int compile(String[] args) {

    List<String> errors = null;
    JKlasyParser parser = null;
    
    try {  
      if (args.length != 1) {
        throw new CompilerArgumentException("Invalid usage!");
      }
      String file = this.getFileName(args[0]);
      file += "Main"; 
        
      // parsing phase
      JKlasyLexer lexer = new JKlasyLexer(new ANTLRFileStream(args[0]));
      CommonTokenStream tokens = new CommonTokenStream(lexer);
      parser = new JKlasyParser(tokens);            
      JKlasyParser.program_return result = parser.program();
      errors = parser.getErrors();      
      if (errors.size() > 0) {
        throw new ParsingErrorException("Parsing errors:", errors);  
      }
      
      // generating tree
      Tree t = (Tree)result.getTree();
      CommonTreeNodeStream nodes = new CommonTreeNodeStream(t);
      nodes.setTokenStream(tokens);
      
      // types init phase
      JKlasyTypesInit typesinit = new JKlasyTypesInit(nodes);      
      Environment env = typesinit.program();
      errors = env.getErrors();
      if(errors.size() > 0) {
        throw new TypesInitErrorException("Types init errors:", errors);
      }      
      env.reset();
      
      // generating tree
      t = (Tree)result.getTree();
      nodes =  new CommonTreeNodeStream(t);
      nodes.setTokenStream(tokens);
      
      // type checking phase
      JKlasyTypeChecker typechecker = new JKlasyTypeChecker(nodes);
      typechecker.setEnvironment(env);
      env = typechecker.program();
      errors = env.getErrors();
      if(errors.size() > 0) {
        throw new TypeCheckerErrorException("Type checker errors:", errors);
      }      
      env.reset();
     
      HashMap<String, String> emitmap = (HashMap<String, String>)
              env.makeMethodsSignatures(); 
      
      // generating tree
      t = (Tree)result.getTree();
      nodes =  new CommonTreeNodeStream(t);
      nodes.setTokenStream(tokens);
      
      // code emiting phase
      FileReader groupFileReader = 
        new FileReader("JKlasyEmiterDefinitions.stg");
      StringTemplateGroup templates =
      new StringTemplateGroup(groupFileReader);
      groupFileReader.close();
      
      JKlasyEmiter emiter =
        new JKlasyEmiter(nodes);
      
      emiter.setTemplateLib(templates);
      program_return emissionResult = 
        emiter.program(new EmiterEnv(emitmap, env.getTypes(), env.getGlobalFuns()), file);
      
      for(String filename : emissionResult.createdFiles) {
        jasmin.Main.assemble(filename);
      }
      
    } catch (RecognitionException e) {
        System.out.println(e.getMessage());
      return EXIT_FAILURE;
      
    } catch (IOException e) {
        System.out.println("IO exception occured.");
        System.out.println(e.getMessage());
        return EXIT_FAILURE;
        
    } catch (IllegalArgumentException e) {
        System.out.println("JKlasy compiler usage:"
          + " java -jar jklasy filename");
        
        System.out.println(e.getMessage());
        
        return EXIT_FAILURE;
    } catch (RewriteEmptyStreamException e) {
      /* Few invalid parsing tests throw this exception (e.g. test01.jkl),
       * but it's quite stupid - we wan't to know what the parsing error is, but
       * this exception isn't closely related with parsing. So thats why we look 
       * for parser errors (parser.getErrors())
       */
      errors = parser.getErrors();
      System.out.println("Parsing errors:");
      for(String error : errors)
        System.out.println(error);
        
        return EXIT_FAILURE;
    }  
    catch (ParsingErrorException e)
    {
      /* Maybe we wish to take some very sophisticated
       * action upon parsing errors.
       */      
      System.out.println("Parsing errors:");
      for(String error : errors)
      {
        System.out.println(error);
      }
      return EXIT_FAILURE;
      
    } catch (TypesInitErrorException e)
    {      
       /* Maybe we wish to take some very sophisticated
        * action upon parsing errors.
        */
      
       System.out.println("Type system initialization failed.");
       System.out.println(e.getMessage());
       for(String error : errors)
       {
         System.out.println(error);
       }
       return EXIT_FAILURE;
    } catch (TypeCheckerErrorException e)
    {      
      /* Maybe we wish to take some very sophisticated
       * action upon parsing errors.
       */
     
      System.out.println("Type checker error occured.");
      System.out.println(e.getMessage());
      for(String error : errors)
      {
        System.out.println(error);
      }
      return EXIT_FAILURE;
   } 
   catch (Exception e)
   {      
      System.out.println("Unknown error occured.");
      System.out.println(e.getMessage());
      e.printStackTrace();
      
      return EXIT_FAILURE;
   }
   return EXIT_SUCCESS;
  }
}
